﻿using System;
using System.Data.Common;

using Centido.Core;

namespace Centido.Management.ConsoleManager
{
    /// <summary>
    ///   A console command that installs LoBDb.NET metadata.
    /// </summary>
    internal class InstallCommand : PreLoginCommandBase
    {
        /// <summary>
        ///   The application user name parameter.
        /// </summary>
        public const string AppUserNameParameter = "appuser";

        /// <summary>
        ///   The application user password parameter.
        /// </summary>
        public const string AppUserPasswordParameter = "appassword";

        /// <summary>
        ///   The admin user name parameter.
        /// </summary>
        public const string AdminUserNameParameter = "adminuser";

        /// <summary>
        ///   The admin user password parameter.
        /// </summary>
        public const string AdminUserPasswordParameter = "adminpassword";


        /// <summary>
        ///   Gets the console command name.
        /// </summary>
        public override string Name
        {
            get { return "install"; }
        }


        /// <summary>
        ///   Gets the console command description.
        /// </summary>
        public override string Description
        {
            get { return Messages.InstallCommandDescription; }
        }


        /// <summary>
        ///   Gets the console command full description.
        /// </summary>
        public override string FullDescription
        {
            get { return Messages.InstallCommandFullDescription; }
        }


        /// <summary>
        ///   Checks the console command parameters.
        /// </summary>
        /// <param name="context">The console command execution context.</param>
        /// <returns>Returns true if everything is ok; otherwise, returns false.</returns>
        protected override bool CheckCommandParameters(CommandExecutionContext context)
        {
            var result = base.CheckCommandParameters(context);

            // Checking the application user name and password
            result = result && context.ParameterValueIsNotEmpty(AppUserNameParameter)
                && context.ParameterValueIsNotEmpty(AppUserPasswordParameter);

            // Checking the admin user name and password
            result = result && context.ParameterValueIsNotEmpty(AdminUserNameParameter)
                && context.ParameterValueIsNotEmpty(AdminUserPasswordParameter);

            if (!result)
                Console.WriteLine(Messages.InstallCommandNotAllParametersSpecifiedMessage);

            return result;
        }


        /// <summary>
        ///   Executes the console command.
        /// </summary>
        /// <param name="context">The console command execution context.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="context"/> is null.</exception>
        public override void Execute(CommandExecutionContext context)
        {
            base.Execute(context);

            if (!CheckCommandParameters(context)) return;

            // Getting the provider name
            var providerName = context.GetParameterValue(ProviderNameParameter);

            // Searching for a connection builder
            var connStrBuilder = ConnectionStringBuilders.GetBuilder(providerName);

            if (connStrBuilder == null)
            {
                Console.WriteLine(Messages.NoConnectionBuilderError);
                return;
            }

            // Searching for an installer
            var installer = Installers.GetInstaller(connStrBuilder.ProductName);

            if (installer == null)
            {
                Console.WriteLine(Messages.NoInstallerError);
                return;
            }

            // Building a connection string for installing application user
            var fullConnString = PopulateConnectionString(context);

            if (string.IsNullOrEmpty(fullConnString)) return;

            // Omitting the db name in the connection string
            connStrBuilder.Parse(fullConnString);
            connStrBuilder.DbName = string.Empty;

            // The database name
            var dbName = context.GetParameterValue(DbNameParameter) ?? connStrBuilder.DbName;

            installer.DbProviderName = providerName;
            installer.DbConnectionString = connStrBuilder.Build();

            // Getting the application user credentials
            var appUserName = context.GetParameterValue(AppUserNameParameter);
            var appUserPassword = context.GetParameterValue(AppUserPasswordParameter);

            try
            {
                // Installing the app user
                Console.WriteLine(Messages.InstallCommandInstallingAppUserMessage);

                installer.CreateAppUser(appUserName, appUserPassword, dbName);

                // Changing credentials to the application user
                connStrBuilder.Parse(fullConnString);

                connStrBuilder.DbName = dbName;
                connStrBuilder.UserName = appUserName;
                connStrBuilder.UserPassword = appUserPassword;

                installer.DbConnectionString = (fullConnString = connStrBuilder.Build());

                // Installing the metadata
                Console.WriteLine(Messages.InstallCommandInstallingMetadataMessage);

                installer.Install();
            }
            catch (ArgumentException e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine();

                return;
            }
            catch (DbException e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine();

                // Displaying the connection string
                Console.WriteLine(Messages.UsedConnectionStringHint,
                    installer.DbConnectionString);
                return;
            }

            // Getting the admin user credentials
            var adminUserName = context.GetParameterValue(AdminUserNameParameter);
            var adminUserPassword = context.GetParameterValue(AdminUserPasswordParameter);

            // Logging in as an application user
            var domainConfig = new DomainConfiguration
            {
                DbConnectionString = fullConnString,
                DbProviderName = context.GetParameterByName(ProviderNameParameter).FirstValue,
            };

            // The schema parameter
            if (context.ParameterValueIsNotEmpty(
                    SchemaParameter,
                    SchemaShortParameter))
                domainConfig.DbSchemaName = context.GetParameterValue(SchemaParameter, SchemaShortParameter);

            try
            {
                // Installing the admin user
                Console.WriteLine(Messages.InstallCommandInstallingAdminUserMessage);

                var domain = Domain.Build(domainConfig);

                var adminUser = domain.AddUser(adminUserName, adminUserPassword);

                adminUser.CanChangeSchema = true;
                adminUser.CanChangeSecurity = true;
            }
            catch (ArgumentException e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine();

                return;
            }
            catch (DbException e)
            {
                // Displaying the error message
                Console.WriteLine(e.Message);
                Console.WriteLine();

                // Displaying the connection string
                Console.WriteLine(Messages.UsedConnectionStringHint,
                    domainConfig.DbConnectionString);
                return;
            }

            // Displaying the success message
            Console.WriteLine(Messages.CommandCompletedMessage);
        }
    }
}
